﻿﻿using System;
using System.Collections;
﻿using System.IO;
using System.Net;
﻿using System.Threading;
﻿using System.Windows;
﻿using Newtonsoft.Json.Linq;
﻿using XBMCRemote.DataModel.Entities;

namespace XBMCRemote.DataModel
{
    public class JsonRpcClient : WebRequest
    {
        private int _id;
        private Uri ClientUri;
        private Configuration _configuration;

        private JObject _data;

        public EventHandler<EventArgs<object>> PingResult; 
        public EventHandler<EventArgs<object>> GetMoviesResult; 

        public JsonRpcClient(Configuration configuration)
        {
            _configuration = configuration;
            ClientUri = new Uri(string.Format("http://{0}:{1}/jsonrpc", configuration.Host, configuration.Port));
        }

        public void Invoke(string method)
        {
            Invoke(method, null, null);
        }

        public void Invoke(string method, object[] args)
        {
            Invoke(method, args, null);
        }

        public void Invoke(string method, object args)
        {
            Invoke(method, args, null);
        }

        public void Invoke(string method, IDictionary args)
        {
            Invoke(method, args, null);
        }

        public virtual void Invoke(string method, object args, int? timeout)
        {
            if (method == null)
                throw new ArgumentNullException("method");
            if (method.Length == 0)
                throw new ArgumentException(null, "method");

            if (_id > 100000)
                _id = 0;

            _data = new JObject();
            _data.Add(new JProperty("jsonrpc", "2.0"));
            _data.Add(new JProperty("method", method));
            _data.Add(new JProperty("id", ++_id));
            if (args != null)
                _data.Add(new JProperty("params", args));

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ClientUri);
            request.Credentials = new NetworkCredential(_configuration.Username,_configuration.Password);
            request.ContentType = "application/json";
            request.Method = "POST";

            try
            {
                request.BeginGetRequestStream(ar =>
                                                  {
                                                    var originalRequestStream = request.EndGetRequestStream(ar);
                                                    using (var writer = new StreamWriter(originalRequestStream))
                                                    {
                                                        writer.Write(_data.ToString());
                                                    }

                                                    request.BeginGetResponse(a =>
                                                                                {
                                                                                    try
                                                                                    {
                                                                                        var response = request.EndGetResponse(a);
                                                                                        var responseStream = response.GetResponseStream();
                                                                                        using (var reader = new StreamReader(responseStream))
                                                                                        {
                                                                                            object responseValue = OnResponse(reader);
                                                                                            HandleResponse(responseValue, method);
                                                                                        }
                                                                                    }
                                                                                    catch (Exception exception)
                                                                                    {
                                                                                        object responseValue = "error";
                                                                                        HandleResponse(responseValue, method);
                                                                                    }
                                                                                }, null);
                                                  }, null);
            }
            catch (Exception ex)
            {
                object responseValue = "error";
                HandleResponse(responseValue, method);
            }
        }

        public void SendKeyCode(KeyCode key)
        {
            Uri uri =
                new Uri(string.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command=SendKey({2})&datetime={3}", _configuration.Host,
                                      _configuration.Port, (int)key, DateTime.Now.Ticks));
            WebClient request = new WebClient
                              {
                                  Credentials = new NetworkCredential(_configuration.Username, _configuration.Password)
                              };
            request.DownloadStringAsync(uri);
        }

        public void SendActionCode(ActionCode actionKey)
        {
            Uri uri =
                new Uri(string.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command=Action({2})&datetime={3}", _configuration.Host,
                                      _configuration.Port, (int)actionKey, DateTime.Now.Ticks));
            WebClient request = new WebClient
                              {
                                  Credentials = new NetworkCredential(_configuration.Username, _configuration.Password)
                              };
            request.DownloadStringAsync(uri);
        }

        private object OnResponse(StreamReader reader)
        {
            var JObjectResponse = JObject.Parse(reader.ReadToEnd());
            var members = JObjectResponse.Properties();

            foreach (var member in members)
            {
                if (string.CompareOrdinal(member.Name, "error") == 0)
                {
                    var errorObject = (JObject)member.Value;
                    if (errorObject != null)
                        OnError(errorObject);
                }
                else if (string.CompareOrdinal(member.Name, "result") == 0)
                {
                    if (member.Value.HasValues == true)
                    {
                        return (JObject)member.Value;
                    }
                    else
                    {
                        if (member.Value.Type == JTokenType.Integer)
                            return (int)member.Value.Value<JValue>();
                        else if (member.Value.Type == JTokenType.Float)
                            return (float)member.Value.Value<JValue>();
                        else
                            return (string)member.Value.Value<JValue>();
                    }
                }
            }
            throw new Exception("Invalid JSON-RPC response. It contains neither a result nor error.");
        }

        protected virtual void OnError(JObject errorValue)
        {
            if (errorValue != null)
                throw new Exception(errorValue["code"].Value<JValue>().Value.ToString() + ": " + errorValue["message"].Value<JValue>().Value.ToString());
            else
                throw new Exception("Unknown error occurred");
        }

        private void HandleResponse(object response, string methodName)
        {
            var args = new EventArgs<object>{Data = response};
            switch (methodName)
            {
                case "JSONRPC.Ping":
                    if(PingResult != null)
                        PingResult(null, args);
                    break;
                case "VideoLibrary.GetMovies":
                    if (GetMoviesResult != null)
                        GetMoviesResult(null, args);
                    break;

            }
        }
    }
}
